package com.vmj.natives.java.lang;

import com.vmj.heap.*;
import com.vmj.heap.VMClass;
import com.vmj.heap.ClassLoader;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.MethodInvokeLogic;
import com.vmj.natives.NativeMethod;
import com.vmj.natives.Registry;
import com.vmj.rtda.Frame;
import com.vmj.rtda.LocalVars;
import com.vmj.rtda.OperandStack;
import com.vmj.rtda.Thread;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by yangshoujun on 2017/9/16 下午5:18.
 */
public class System {

    public static final java.lang.String jlSystem = "java/lang/System";

    public static void init() {
        Registry.register(jlSystem, "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject src = vars.getRef(0);
                int srcPos = vars.getInt(1);
                VMObject dest = vars.getRef(2);
                int destPos = vars.getInt(3);
                int length = vars.getInt(4);

                if(src == null || dest == null) {
                    throw new RuntimeException("java.lang.NullPointerException");
                }

                if(!checkArrayCopy(src, dest)) {
                    throw new RuntimeException("java.lang.ArrayStoreException");
                }

                if(srcPos < 0 || destPos < 0 || length < 0 || (srcPos + length > ArrayObject.getArrayLength(src))
                        || (destPos + length > ArrayObject.getArrayLength(dest))) {
                    throw new RuntimeException("java.lang.IndexOutOfBoundsException");
                }

                arrayCopy(src, srcPos, dest, destPos, length);
            }
        });

        Registry.register(jlSystem, "initProperties", "(Ljava/util/Properties;)Ljava/util/Properties;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject props = vars.getRef(0);
                OperandStack stack = frame.getOperandStack();
                stack.pushRef(props);
                Method setProperty = frame.getMethod().getClazz().getStaticMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
                Thread thread = frame.getThread();
                ClassLoader classLoader = frame.getMethod().getClazz().getClassLoader();
                Set<java.lang.String> keys = sysProps.keySet();
                for(java.lang.String key : keys) {
                    VMObject keyObj = StringPool.getStringObject(classLoader, key);
                    VMObject valueObj = StringPool.getStringObject(classLoader, sysProps.get(key));
                    OperandStack ops = new OperandStack(3);
                    ops.pushRef(props);
                    ops.pushRef(keyObj);
                    ops.pushRef(valueObj);
                    Frame shimFrame = new Frame(thread, ShimMethod.getShipReturnMethod(), ops);
                    thread.pushFrame(shimFrame);
                    MethodInvokeLogic.invokeMethod(shimFrame, setProperty);
                }
            }
        });

        Registry.register(jlSystem, "identityHashCode", "(Ljava/lang/Object;)I", new NativeMethod() {
            public void execute(Frame frame) {
                VMObject _this = frame.getLocalVars().getThis();
                int hashCode = this.hashCode();
                frame.getOperandStack().pushInt(hashCode);
            }
        });

        Registry.register(jlSystem, "setIn0", "(Ljava/io/InputStream;)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject in = vars.getRef(0);
                VMClass systemClass = frame.getMethod().getClazz();
                systemClass.setRefVar("in", "Ljava/io/InputStream;", in);
            }
        });

        Registry.register(jlSystem, "setOut0", "(Ljava/io/InputStream;)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject out = vars.getRef(0);
                VMClass systemClass = frame.getMethod().getClazz();
                systemClass.setRefVar("out", "Ljava/io/InputStream;", out);
            }
        });

        Registry.register(jlSystem, "setErr0", "(Ljava/io/InputStream;)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject error = vars.getRef(0);
                VMClass systemClass = frame.getMethod().getClazz();
                systemClass.setRefVar("err", "Ljava/io/InputStream;", error);
            }
        });

        Registry.register(jlSystem, "currentTimeMillis", "()J", new NativeMethod() {
            public void execute(Frame frame) {
                frame.getOperandStack().pushLong(java.lang.System.currentTimeMillis());
            }
        });

        Registry.register(jlSystem, "setOut0", "(Ljava/io/PrintStream;)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject out = vars.getRef(0);
                VMClass sysClass = frame.getMethod().getClazz();
                sysClass.setRefVar("out", "Ljava/io/PrintStream;", out);
            }
        });

        Registry.register(jlSystem, "setErr0", "(Ljava/io/PrintStream;)V", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject err = vars.getRef(0);
                VMClass sysClass = frame.getMethod().getClazz();
                sysClass.setRefVar("err", "Ljava/io/PrintStream;", err);
            }
        });

        Registry.register(jlSystem, "mapLibraryName", "(Ljava/lang/String;)Ljava/lang/String;", new NativeMethod() {
            public void execute(Frame frame) {
                LocalVars vars = frame.getLocalVars();
                VMObject paramStr = vars.getRef(0);
                java.lang.String str = paramStr.getRefString();
                if("zip".equals(str.toLowerCase())) {
                    VMObject ret = StringPool.getStringObject(frame.getMethod().getClazz().getClassLoader(), "libzip.dylib");
                    frame.getOperandStack().pushRef(ret);
                } else {
                    java.lang.System.out.println("mapLibraryName: " + str);
                }
            }
        });
    }

    private static void arrayCopy(VMObject src, int srcPos, VMObject dest, int destPos, int length) {
        java.lang.Object data = src.getData();
        if(data instanceof byte[]) {
            byte[] srcData = (byte[]) src.getData();
            byte[] destData = (byte[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof short[]) {
            int[] srcData = (int[]) src.getData();
            int[] destData = (int[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof double[]) {
            double[] srcData = (double[]) src.getData();
            double[] destData = (double[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof char[]) {
            char[] srcData = (char[]) src.getData();
            char[] destData = (char[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof int[]) {
            int[] srcData = (int[]) src.getData();
            int[] destData = (int[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof float[]) {
            float[] srcData = (float[]) src.getData();
            float[] destData = (float[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof long[]) {
            long[] srcData = (long[]) src.getData();
            long[] destData = (long[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else if(data instanceof VMObject[]) {
            VMObject[] srcData = (VMObject[]) src.getData();
            VMObject[] destData = (VMObject[]) dest.getData();
            java.lang.System.arraycopy(srcData, srcPos, destData, destPos, length);
        } else {
            throw new RuntimeException("Invalid array type: " + src.getClazz());
        }
    }

    private static boolean checkArrayCopy(VMObject src, VMObject dest) {
        VMClass srcClass = src.getClazz();
        VMClass destClass = dest.getClazz();

        if(srcClass.getName().equals("java/lang/String") && destClass.getName().equals("[C")) {
            return true;
        }

        if(!srcClass.isArray() || !destClass.isArray()) {
            return false;
        }

        if(srcClass.isPrimitive() || destClass.isPrimitive()) {
            return srcClass == destClass;
        }

        return true;
    }

    private static final Map<java.lang.String, java.lang.String> sysProps = new HashMap<java.lang.String, java.lang.String>();

    static {
        sysProps.put("java.version",         "1.8.0");
        sysProps.put("java.vendor",          "shoujun.yang");
        sysProps.put("java.vendor.url",      "https://gitee.com/skyz/vm-j");
        sysProps.put("java.home",            "todo");
        sysProps.put("java.class.version",   "52.0");
        sysProps.put("java.class.path",      "todo");
        sysProps.put("java.awt.graphicsenv", "sun.awt.CGraphicsEnvironment");
        sysProps.put("os.name",              "any os");
        sysProps.put("os.arch",              "any arch");
        sysProps.put("os.version",           "");
        sysProps.put("file.separator",       File.separator);
        sysProps.put("path.separator",       ",");
        sysProps.put("line.separator",       "\n");
        sysProps.put("user.name",            "");
        sysProps.put("user.home",            "");
        sysProps.put("user.dir",             ".");
        sysProps.put("user.country",         "CN");
        sysProps.put("file.encoding",        "UTF-8");
        sysProps.put("sun.stdout.encoding",  "UTF-8");
        sysProps.put("sun.stderr.encoding",  "UTF-8");

    }
}
